//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#include "VoxelFile.h"

namespace Voxels
{
	
/*
=================================================
	constructor
=================================================
*/
	VoxelFile::VoxelFile () :
		_useMemCache(true), _borderCacheEnabled(true)
	{
	}
	
/*
=================================================
	destructor
=================================================
*/
	VoxelFile::~VoxelFile ()
	{
		Close();
	}
	
/*
=================================================
	Open
=================================================
*/
	bool VoxelFile::Open (uni_c_string filePath)
	{
		Close();
		
		CHECK_ARGUMENT( FileSystem::IsDirectoryExist( filePath ) );

		RFilePtr	file;
		string		fname = filePath;	_GetMinFileName( fname );

		CHECK_RETURN( (file = File::HddRFile::New( fname )).IsNotNull() );

		CHECK_RETURN( file->Read( _header ) );

		// check header
		CHECK_VALUE( _header.version == VERSION );
		CHECK_VALUE( _header.headerSize == sizeof(_header) );

		_filePath = filePath;

		CHECK_RETURN( _Init() );

		CHECK_RETURN( _UpdateAllCache() );
		return true;
	}
	
/*
=================================================
	Create
=================================================
*/
	bool VoxelFile::Create (uni_c_string filePath, const uvec3 &blockSize, const uvec3 &numBlocks, uint bytePerVoxel, uint voxelFormatId)
	{
		Close();

		WFilePtr	file;
		string		fname = filePath;	_GetMinFileName( fname );

		if ( not FileSystem::IsDirectoryExist( filePath ) )
		{
			CHECK_RETURN( FileSystem::NewDirectory( filePath ) );
		}

		CHECK_RETURN( (file = File::HddWFile::New( fname )).IsNotNull() );

		_header.bytePerVoxel	= bytePerVoxel;
		_header.voxelFormatId	= voxelFormatId;
		_header.blockSize		= blockSize;
		_header.numBlocks		= numBlocks;
		_header.headerSize		= sizeof(_header);
		
		CHECK_RETURN( _Init() );

		_header.pixelPerVoxel	= _blockWithBorderInfo.pixelPerVoxel;
		_header.texSize			= _blockWithBorderInfo.tex2dSize;
		_header.numLayers		= _blockWithBorderInfo.numLayers;

		CHECK_RETURN( file->Write( _header ) );

		_filePath = filePath;
		return true;
	}
	
/*
=================================================
	IsExist
=================================================
*/
	bool VoxelFile::IsExist (uni_c_string filePath) const
	{
		string		fname = filePath;
		_GetMinFileName( fname );

		return FileSystem::IsFileExist( fname );
	}

/*
=================================================
	_Init
=================================================
*/
	bool VoxelFile::_Init ()
	{
		// create block info //
		_blockWithBorderInfo.blockSize		= _header.blockSize;
		_blockWithBorderInfo.border			= 1;
		_blockWithBorderInfo.bytePerVoxel	= _header.bytePerVoxel;

		CHECK_RETURN( _GetCLImageFormat( _blockWithBorderInfo.bytePerVoxel, _blockWithBorderInfo.clFormat,
										 _blockWithBorderInfo.pixelPerVoxel ) );

		if ( _header.pixelPerVoxel != 0 )
		{
			CHECK_VALUE( _header.pixelPerVoxel == _blockWithBorderInfo.pixelPerVoxel );
		}

		if ( IsZero( _header.texSize ) or IsZero( _header.numLayers ) )
		{
			Helpers::CalcTex2DSizeForBlock( _blockWithBorderInfo.tex2dSize, _blockWithBorderInfo.numLayers,
											_blockWithBorderInfo.blockSize + _blockWithBorderInfo.border*2,
											_blockWithBorderInfo.pixelPerVoxel, uvec2(MAX_TEX2D_SIZE) );
		}
		else
		{
			_blockWithBorderInfo.tex2dSize	= _header.texSize;
			_blockWithBorderInfo.numLayers	= _header.numLayers;
		}
		
		_blockWithBorderInfo.xLayersPOT = GetPOT( _blockWithBorderInfo.numLayers.x );

		CHECK_VALUE( _blockWithBorderInfo.numLayers.x == (1 << _blockWithBorderInfo.xLayersPOT) );


		// create cache buffer //
		CHECK_ALLOC( _cache.Reallocate( NumBlocks().Volume() ) );

		FOR( i, _cache ) {
			_cache[i] = VoxelsBlockCache( GetBlockWithBorderInfo() );
		}


		// create border cache //
		_border = VoxelsBlockCacheInBorder( GetBlockWithBorderInfo() );

		return true;
	}
	
/*
=================================================
	GetCLImageFormat
----
	pixelsPerVoxel - if voxel size is too big then
	width of texture increased to (width * pixelsPerVoxel)
=================================================
*/
	bool VoxelFile::_GetCLImageFormat (uint bytePerVoxel, OUT cl_img_format &format, OUT uint &pixelsPerVoxel)
	{
		CHECK_ARGUMENT( bytePerVoxel != 0 );

		if ( bytePerVoxel % sizeof(ColorFormat::RGBA32u) == 0 )
		{
			format			= cl_img_format( cl_img_channel_order::RGBA, cl_img_channel::UINT32 );
			pixelsPerVoxel	= bytePerVoxel / sizeof(ColorFormat::RGBA32u);
			return true;
		}
		
		if ( bytePerVoxel % sizeof(ColorFormat::RG32u) == 0 )
		{
			format			= cl_img_format( cl_img_channel_order::RG, cl_img_channel::UINT32 );
			pixelsPerVoxel	= bytePerVoxel / sizeof(ColorFormat::RG32u);
			return true;
		}

		if ( bytePerVoxel % sizeof(ColorFormat::R32u) == 0 )
		{
			format			= cl_img_format( cl_img_channel_order::R, cl_img_channel::UINT32 );
			pixelsPerVoxel	= bytePerVoxel / sizeof(ColorFormat::R32u);
			return true;
		}
		
		return false;
	}

/*
=================================================
	Close
=================================================
*/
	void VoxelFile::Close ()
	{
		CHECK_WARNING( Flush() );

		_fileMemCache.Clear();
		_useMemCache = true;

		_filePath.Empty();
		_header = MainHeader();
	}

/*
=================================================
	Load
----
	load block with border
=================================================
*/
	bool VoxelFile::Load (const ivec3 &blockNumber, OUT VoxelsBlock &block)
	{
		FUNC_PROFILER();

		CHECK_ARGUMENT( All( blockNumber >= ivec3(0) ) and All( blockNumber < NumBlocks().To<ivec3>() ) );

		VoxelsBlockPtr block_ptr = &block;

		CHECK_RETURN( _LoadBlock( blockNumber, block_ptr ) );

		if ( IsZero( block.Border() ) )
			return true;
		
		CHECK_RETURN( _CopyCache( blockNumber, block ) );
		return true;
	}
	
/*
=================================================
	Load
----
	load block with border
=================================================
*/
	bool VoxelFile::Load (const ivec3 &blockNumber, OUT VoxelsBlockPtr &block)
	{
		FUNC_PROFILER();

		CHECK_ARGUMENT( All( blockNumber >= ivec3(0) ) and All( blockNumber < NumBlocks().To<ivec3>() ) );

		CHECK_RETURN( _LoadBlock( blockNumber, block ) );

		if ( IsZero( block->Border() ) )
			return true;
		
		CHECK_RETURN( _CopyCache( blockNumber, *block ) );
		return true;
	}

/*
=================================================
	Store
=================================================
*/
	bool VoxelFile::Store (const ivec3 &blockNumber, const VoxelsBlock &block)
	{
		FUNC_PROFILER();

		CHECK_ARGUMENT( All( blockNumber >= ivec3(0) ) and All( blockNumber < NumBlocks().To<ivec3>() ) );
		CHECK_ARGUMENT( block.IsValid() );

		CHECK_RETURN( _StoreBlock( blockNumber, block ) );
		CHECK_RETURN( _CreateCache( blockNumber, block ) );
		return true;
	}
	
/*
=================================================
	Flush
=================================================
*/
	bool VoxelFile::Flush ()
	{
		if ( not _fileMemCache.IsChanged() )
			return true;

		FUNC_PROFILER();

		const bool prev_value = _useMemCache;

		_useMemCache = false;

		for (uint z = 0; z < NumBlocks().z; ++z)
		{
			for (uint y = 0; y < NumBlocks().y; ++y)
			{
				for (uint x = 0; x < NumBlocks().x; ++x)
				{
					VoxelsBlockPtr	block;
					const ivec3		number = ivec3(x, y, z);
					const uint		index  = _BlockNumberToIndex( number );

					if ( _fileMemCache.Load( index, block ) )
					{
						CHECK_RETURN( _StoreBlock( number, *block ) );
					}

					CHECK_RETURN( _StoreCache( number ) );
				}
			}
		}

		_useMemCache = prev_value;

		_fileMemCache.Flush();
		return true;
	}
	
/*
=================================================
	EnableBorderCache
=================================================
*/
	void VoxelFile::EnableBorderCache (bool enabled)
	{
		if ( _borderCacheEnabled == enabled )
			return;

		_borderCacheEnabled = enabled;

		if ( _borderCacheEnabled )
		{
			CHECK_RETURN_R( _CreateAllCache(), );
		}
	}

/*
=================================================
	_LoadBlock
=================================================
*/
	bool VoxelFile::_LoadBlock (const ivec3 &blockNumber, OUT VoxelsBlockPtr &block)
	{
		FUNC_PROFILER();

		if ( _useMemCache )
		{
			if ( _fileMemCache.Load( _BlockNumberToIndex( blockNumber ), block ) )
				return true;
		}


		string	fname = _filePath;
		_GetBlockFileName( blockNumber, fname );

		CHECK_ARGUMENT( FileSystem::IsFileExist( fname ) );

		RFilePtr	file;
		CHECK_RETURN( (file = File::HddRFile::New( fname )).IsNotNull() );

		BlockFileHeader	header;
		CHECK_RETURN( file->Read( header ) );
		CHECK_RETURN( _CheckHeader( header ) );

		if ( block.IsNull() )
		{
			block = new VoxelsBlock( GetBlockWithBorderInfo() );
			CHECK_RETURN( block->Create() );
		}

		CHECK_RETURN( block->IsValid() );

		CHECK_VALUE( header.dataSize == block->Data().Size() );

		CHECK_RETURN( file->Read( block->Data().ptr(), block->Data().Size() ) );

		return true;
	}
	
/*
=================================================
	_CopyCache
=================================================
*/
	bool VoxelFile::_CopyCache (const ivec3 &blockNumber, OUT VoxelsBlock &block)
	{
		FUNC_PROFILER();

		CHECK_RETURN( _UpdateAllCache() );

		for (int z = -BORDER; z <= BORDER; ++z)
		{
			for (int y = -BORDER; y <= BORDER; ++y)
			{
				for (int x = -BORDER; x <= BORDER; ++x)
				{
					const ivec3	dir			= ivec3( x, y, z );
					const ivec3	block_coord	= blockNumber + dir;
					
					if ( IsZero( dir ) )
						continue;

					if ( Any( block_coord < ivec3(0) ) or Any( block_coord >= NumBlocks().To<ivec3>() ) )
					{
						CHECK_RETURN( _border.CopyTo( dir, block ) );
					}
					else
					{
						CHECK_RETURN( _GetCache( block_coord ).CopyTo( dir, block ) );
					}
				}
			}
		}
		return true;
	}
	
/*
=================================================
	_GetCache
=================================================
*/
	VoxelsBlockCache& VoxelFile::_GetCache (const ivec3 &blockNumber)
	{
		const ivec3	num_blocks = NumBlocks().To<ivec3>();

		const int	i = blockNumber.x + blockNumber.y * num_blocks.x + blockNumber.z * num_blocks.x * num_blocks.y;

		VoxelsBlockCache & cache = _cache[ i ];

		return cache;
	}
	
/*
=================================================
	_UpdateAllCache
=================================================
*/
	bool VoxelFile::_UpdateAllCache ()
	{
		FUNC_PROFILER();

		CHECK_VALUE( _cache.Count() == NumBlocks().Volume() );
		
		VoxelsBlockPtr	tmp;

		for (uint z = 0; z < NumBlocks().z; ++z)
		{
			for (uint y = 0; y < NumBlocks().y; ++y)
			{
				for (uint x = 0; x < NumBlocks().x; ++x)
				{
					const ivec3	block_number = ivec3( x, y, z );

					VoxelsBlockCache& cache = _GetCache( block_number );

					if ( cache.IsValid() )
						continue;

					if ( _LoadCache( block_number ) )
						continue;

					CHECK_RETURN( _LoadBlock( block_number, tmp ) );
					CHECK_RETURN( _CreateCache( block_number, *tmp ) );
				}
			}
		}

		return true;
	}
	
/*
=================================================
	_CreateAllCache
=================================================
*/
	bool VoxelFile::_CreateAllCache ()
	{
		FUNC_PROFILER();

		CHECK_VALUE( _cache.Count() == NumBlocks().Volume() );
		
		VoxelsBlockPtr	tmp;

		for (uint z = 0; z < NumBlocks().z; ++z)
		{
			for (uint y = 0; y < NumBlocks().y; ++y)
			{
				for (uint x = 0; x < NumBlocks().x; ++x)
				{
					const ivec3	block_number = ivec3( x, y, z );

					CHECK_RETURN( _LoadBlock( block_number, tmp ) );
					CHECK_RETURN( _CreateCache( block_number, *tmp ) );
				}
			}
		}
		return true;
	}
	
/*
=================================================
	_StoreAllCache
=================================================
*/
	bool VoxelFile::_StoreAllCache ()
	{
		FUNC_PROFILER();
		
		CHECK_VALUE( _cache.Count() == NumBlocks().Volume() );
		
		for (uint z = 0; z < NumBlocks().z; ++z)
		{
			for (uint y = 0; y < NumBlocks().y; ++y)
			{
				for (uint x = 0; x < NumBlocks().x; ++x)
				{
					const ivec3	block_number = ivec3( x, y, z );

					CHECK_RETURN( _StoreCache( block_number ) );
				}
			}
		}
		return true;
	}

/*
=================================================
	_StoreBlock
=================================================
*/
	bool VoxelFile::_StoreBlock (const ivec3 &blockNumber, const VoxelsBlock &block)
	{
		FUNC_PROFILER();
		
		if ( _useMemCache )
		{
			return _fileMemCache.Store( _BlockNumberToIndex( blockNumber ), block );
		}


		string	fname = _filePath;
		_GetBlockFileName( blockNumber, fname );

		WFilePtr	file;
		CHECK_RETURN( (file = File::HddWFile::New( fname )).IsNotNull() );
		
		BlockFileHeader	header;
		_InitHeader( header );

		header.dataSize = block.Data().Size();

		CHECK_RETURN( file->Write( header ) );
		CHECK_RETURN( file->Write( block.Data().ptr(), block.Data().Size() ) );

		return true;
	}

/*
=================================================
	_CreateCache
=================================================
*/
	bool VoxelFile::_CreateCache (const ivec3 &blockNumber, const VoxelsBlock &block)
	{
		if ( not _borderCacheEnabled )
			return true;

		FUNC_PROFILER();

		CHECK_RETURN( _GetCache( blockNumber ).Create( block ) );
		
		CHECK_RETURN( _StoreCache( blockNumber ) );
		return true;
	}

/*
=================================================
	_StoreCache
=================================================
*/
	bool VoxelFile::_StoreCache (const ivec3 &blockNumber)
	{
		FUNC_PROFILER();

		VoxelsBlockCache& cache = _GetCache( blockNumber );

		CHECK_VALUE( cache.IsValid() );

		if ( _useMemCache )
			return true;


		// save to file //
		string	fname = _filePath;
		_GetCacheFileName( blockNumber, fname );
		
		WFilePtr	file;
		CHECK_RETURN( (file = File::HddWFile::New( fname )).IsNotNull() );

		CacheFileHeader	header;
		_InitHeader( header );

		header.dataSize = cache.Data().Size();

		CHECK_RETURN( file->Write( header ) );
		CHECK_RETURN( file->Write( cache.Data().ptr(), cache.Data().Size() ) );

		return true;
	}
	
/*
=================================================
	_LoadCache
=================================================
*/
	bool VoxelFile::_LoadCache (const ivec3 &blockNumber)
	{
		FUNC_PROFILER();

		string	fname = _filePath;
		_GetCacheFileName( blockNumber, fname );
		
		RFilePtr	file;

		if ( (file = File::HddRFile::New( fname )).IsNull() )
			return false;

		CacheFileHeader	header;

		CHECK_RETURN( file->Read( header ) );
		CHECK_RETURN( _CheckHeader( header ) );
		
		CHECK_RETURN( _GetCache( blockNumber ).AllocData() );
		VoxelsBlockCache::VoxelsUniData_t data = _GetCache( blockNumber ).Data();

		CHECK_VALUE( header.dataSize == data.Size() );
		CHECK_RETURN( file->Read( data.ptr(), data.Size() ) );

		return true;
	}
	

}	// Voxels